package ipx

import (
	"fmt"
	"net"
	"os/exec"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/zeromicro/go-zero/core/logx"
)

var (
	// privateIPNets 缓存预解析的私有IP网段
	privateIPNets []*net.IPNet
	once          sync.Once
)

// 初始化私有IP网段
func initPrivateIPNets() {
	// IPv4 私有网段定义
	privateIPBlocks := []string{
		"10.0.0.0/8",     // 10.0.0.0 - 10.255.255.255
		"172.16.0.0/12",  // 172.16.0.0 - 172.31.255.255
		"192.168.0.0/16", // 192.168.0.0 - 192.168.255.255
		"127.0.0.0/8",    // 本地环回地址
		"169.254.0.0/16", // 链路本地地址
	}

	for _, block := range privateIPBlocks {
		_, ipNet, err := net.ParseCIDR(block)
		if err != nil {
			logx.Errorf("ParseCIDR failed: %s, error: %v", block, err)
			continue
		}
		privateIPNets = append(privateIPNets, ipNet)
	}
}

// IsPrivateIP 判断是否为私有 IP
// 支持IPv4和IPv6地址检测
func IsPrivateIP(ip net.IP) bool {
	if ip == nil {
		return false
	}

	// 确保私有IP网段列表已初始化
	once.Do(initPrivateIPNets)

	// 检查预定义的IPv4私有网段
	for _, ipNet := range privateIPNets {
		if ipNet.Contains(ip) {
			return true
		}
	}

	// 检查IPv6本地网络
	if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
		return true
	}

	return false
}

// IsPublicIP 判断是否为公网 IP
func IsPublicIP(ip net.IP) bool {
	return !IsPrivateIP(ip) && !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && !ip.IsMulticast()
}

// IsValidIP checks if a string is a valid IPv4 address
// Returns true if valid, false otherwise
func IsValidIP(ip string) bool {
	// Method 1: Using net.ParseIP (recommended approach)
	parsedIP := net.ParseIP(ip)
	if parsedIP == nil {
		return false
	}

	// Ensure it's an IPv4 address (not IPv6)
	return strings.Contains(ip, ".") && !strings.Contains(ip, ":")
}

func GetPrivateIps() ([]string, error) {
	interfaces, err := net.Interfaces()
	if err != nil {
		return nil, fmt.Errorf("failed to get network interfaces: %v", err)
	}

	ips := make([]string, 0, 1)
	for _, iface := range interfaces {
		addrs, err := iface.Addrs()
		if err != nil {
			logx.Errorf("Failed to get addresses for interface %s: %v", iface.Name, err)
			continue
		}

		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}

			// Check if the IP address is an IPv4 address and private
			if ip != nil && ip.To4() != nil && !ip.IsLoopback() && IsPrivateIP(ip) {
				ips = append(ips, ip.String())
			}
		}
	}
	return ips, nil
}

// IsPingable uses the system's ping command to check if an IP is reachable
func IsPingableCommand(ip string) (bool, error) {
	var cmd *exec.Cmd

	// Different ping commands based on operating system
	switch runtime.GOOS {
	case "windows":
		cmd = exec.Command("ping", "-n", "3", "-w", "2000", ip)
	case "darwin", "linux":
		cmd = exec.Command("ping", "-c", "3", "-W", "2", ip)
	default:
		return false, fmt.Errorf("unsupported operating system")
	}

	// 捕获命令输出以便于调试
	output, err := cmd.CombinedOutput()

	// 分析输出或退出码，而不仅仅依赖错误值
	if err != nil {
		return false, fmt.Errorf("ping error:%v output:%s", err, string(output))
	}

	return true, nil
}

// IsPingableTCP tries to establish a TCP connection to check if host is reachable
// This is an alternative that doesn't require system ping command
func IsPingableTCP(ip, port string, timeout time.Duration) (bool, error) {
	conn, err := net.DialTimeout("tcp", ip+":"+port, timeout)
	if err != nil {
		return false, err
	}
	conn.Close()
	return true, nil
}
